package hibernatemock;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;

import javax.persistence.Entity;

import lombok.Getter;
import lombok.Setter;

public class TreeProcessor {

	@Setter
	private InstanceFactory instanceFactory;
	@Setter
	private MappedFieldsLookup mappedFieldsLookup;
	@Setter
	@Getter
	private Map<Class<?>, Object> avoidCyclic;
	@Setter
	@Getter
	private List<Object> entitiesToPersist;

	public Object processTree(Class<?> clazz, List<Annotation> annotations) throws IllegalArgumentException, IllegalAccessException {
		Object root = instanceFactory.createInstance(clazz, annotations);
		if (clazz.isAnnotationPresent(Entity.class))
			avoidCyclic.put(clazz, root);
		if (clazz.isAnnotationPresent(Entity.class)) {
			for (Field field : mappedFieldsLookup.lookupMappedFields(clazz)) {
				field.setAccessible(true);
				try {
					if (isCollection(field) || field.get(root) == null) {
						Object object = getObject(field);
						field.set(root, object);
					}
				} catch (Exception e) {
					throw new RuntimeException(e);
				}
			}
			entitiesToPersist.add(root);
		}
		return root;
	}

	@SuppressWarnings("unchecked")
	private Object getObject(Field field) throws IllegalAccessException {
		Object property;
		Class<?> fieldType;
		if (isCollection(field)) {
			fieldType = (Class<?>) ((ParameterizedType) field.getGenericType()).getActualTypeArguments()[0];
		} else {
			fieldType = field.getType();
		}
		if (avoidCyclic.containsKey(fieldType)) {
			property = avoidCyclic.get(fieldType);
		} else {
			property = processTree(fieldType, computeAnnotation(field));
		}
		if (isCollection(field)) {
			Collection<Object> collection = (Collection<Object>) instanceFactory.createInstance(field.getType(), computeAnnotation(field));
			int size = collection.size();
			if (size == 0)
				size = 1;
			collection.clear();
			for (int i = 0; i < size; i++)
				collection.add(property);
			return collection;
		}
		return property;
	}

	private boolean isCollection(Field field) {
		return Collection.class.isAssignableFrom(field.getType());
	}

	public List<Annotation> computeAnnotation(Field field) {
		List<Annotation> annotations = new ArrayList<Annotation>();
		for (Annotation annotation : field.getDeclaredAnnotations())
			annotations.add(annotation);
		for (Annotation annotation : field.getType().getDeclaredAnnotations())
			annotations.add(annotation);
		return annotations;
	}

}
